#include "sender.h"   // sender.h
#include "common.h"
#include "communicate.h"


#define TIMEOUT_USEC 1000000  // 超时值（微秒），可根据实际需求调整

// 初始化滑动窗口
void init_sliding_window(SlidingWindow *window, int max_size) {
    window->head = NULL;
    window->tail = NULL;
    window->max_size = max_size;
    window->current_size = 0;
    // 初始化滑动窗口
}


// 从滑动窗口中移除帧
void remove_frame_from_window(LLnode **windows, uint8_t seq_num) {
    LLnode *node = NULL;
    LLnode *nodePrev = NULL;
    LLnode *nodeNext = NULL;
    WindowEntry * tmp = NULL;
    for (int i = 0; i < WINDOW_SIZE; i++) { //遍历窗口，根据seq_num找到其对应监控窗口
        node = *(windows + i);
        if (node == NULL) //不排除窗口的指向为NULL，跳过即可
            continue;

        //node不为NULL的情况下
        tmp = (WindowEntry *)(node->value);
        if (tmp->frame->seq_num == seq_num) {  //找到了entry对象，其中node指向entry结点
            //从Entry链表中删除Entry结点，窗口更新后，如果其指向不为NULL，则必定指向一个entry链表的对象
            nodePrev = node->prev;
            nodeNext = node->next;

            //删除Entry结点
            nodePrev->next = node->next;
            nodeNext->prev = node->prev;
            node->prev = node;
            node->next = node;

            //释放node中相关的内存
            free(tmp->charbuf);
            free(tmp->frame);
            free(tmp);
            free(node);

            //更新窗口的指向
            *(windows + i) = NULL;
        }
    }
}

// 初始化发送方
void init_sender(Sender * sender, int id)
{
    // TODO: 根据需要填充此函数
    sender->send_id = id;                        // 设置发送方的 ID
    sender->input_cmdlist_head.next = &sender->input_cmdlist_head;
    sender->input_cmdlist_head.prev = &sender->input_cmdlist_head;

    sender->input_framelist_head.next = &sender->input_framelist_head;
    sender->input_framelist_head.prev = &sender->input_framelist_head;
}

// 获取发送方的下一个超时时间
void sender_get_next_expiring_timeval(LLnode ** windows, struct timeval *expiring_time) {
    LLnode *current = NULL;
    WindowEntry *tmp = NULL;

    // 初始化最早超时时间
    struct timeval earliestTime;
    gettimeofday(&earliestTime, NULL); 

    //遍历窗口数组，检查各个元素的监控时间，在一定允许范围内，线程可以什么都不做，进入睡眠，等待下一次唤醒
    for (int i = 0; i < WINDOW_SIZE; i++) {
        current = *(windows + i);        
        if (current == NULL)
            continue;

        tmp = (WindowEntry *)current->value;
        if (timeval_usecdiff(&tmp->send_time, &earliestTime) > 0) { //
            earliestTime.tv_sec = tmp->send_time.tv_sec;
            earliestTime.tv_usec = tmp->send_time.tv_usec;
        }
    }
    //最后earlistTime中保存的就是最早的时间 
    expiring_time->tv_sec  = earliestTime.tv_sec;
    expiring_time->tv_usec = earliestTime.tv_usec;
}

LLnode *get_a_entry(LLnode *EntryHeadPtr) {
    LLnode *current = EntryHeadPtr;
    WindowEntry *tmp = NULL;
    while (current->next != EntryHeadPtr) { //遍历Entry链表
        tmp = (WindowEntry *)current->next->value;
        if (tmp->monitor == 0) {
            tmp->monitor = 1;      // 表示已经被窗口监控了
            return current->next;  //返回一个该对象的指针
        }
        current = current->next;
    }
    return NULL;  //如果遍历结束仍没有找到被监控的对象，则返回NULL
}

void update_windows(LLnode *EntryHeadPtr, LLnode **slidingWindows) {
    for (int i = 0; i < WINDOW_SIZE; i++) { //遍历窗口数组，找到指向为NULL的窗口
        if (*(slidingWindows + i) != NULL)
            continue;

        //当指向为NULL时，从entry链表中找到一个没被监控的对象，将其地址保存到窗口中
        *(slidingWindows + i) = get_a_entry(EntryHeadPtr);
    }
}

// 处理接收到的 ACK
void handle_incoming_acks(Sender * sender, LLnode **slidingWindows, LLnode *EntryHeadPtr)
{
    //处理ACK链表中的ACK信息，从其中获取seq_num
    int inframe_queue_length = ll_get_length(&sender->input_framelist_head);
    //fprintf(stdout, "ack list %d\n", inframe_queue_length);
    while (inframe_queue_length > 0) {
        // 从输入队列中弹出一个帧节点
        //fprintf(stdout, "ack list %d\n", inframe_queue_length);
        LLnode *ll_inframe_node = ll_pop_node(&sender->input_framelist_head);

        // 只要ack信息结点出链表了，就可以更新ACK队列长度
        inframe_queue_length = ll_get_length(&sender->input_framelist_head);

        //取出ACK节点中保存的字符串，实际上是一个ACK帧 转化后的字符串
        char *raw_char_buf = (char *)ll_inframe_node->value;

        // 将字符缓冲区转换为帧结构
        Frame *inframe = convert_char_to_frame(raw_char_buf);
        //转换之后实际上就不需要buf了
        free(raw_char_buf);

        // 校验帧是否损坏
        uint16_t received_checksum = inframe->checksum;
        //fprintf(stdout, "sender received ack checksum = %d\n", received_checksum);
        inframe->checksum = 0;  // 清零校验和字段
        //raw_char_buf = convert_frame_to_char(inframe);
        uint16_t computed_checksum = calculate_checksum(inframe);
        //uint16_t computed_checksum = calculate_crc16((const uint8_t *)raw_char_buf, MAX_FRAME_SIZE);
        if (received_checksum != computed_checksum) {
            // 如果帧损坏，丢弃
            //fprintf(stdout, "<SEND-%d>: Corrupted ACK received, dropped\n", sender->send_id);

            //free(inframe);
            free(ll_inframe_node);
            continue;  //继续循环，处理下一个ACK结点
        }

        if (inframe->is_ack && inframe->recv_id == sender->send_id) { // 如果帧是 ACK 并且是发给当前发送方的
            //ack_num中保存的是 和 ACK对应的 帧信息的seq_num
            uint8_t ack_num = inframe->ack_num;  
            //根据ack_num从窗口数组中找到对应的窗口，删除该窗口指向的结点，并更新该窗口的状态
            remove_frame_from_window(slidingWindows, ack_num);
        } 

        // 释放内存
        free(inframe);
        free(ll_inframe_node);
    }
    //当所有ack链表中的信息更新完后，并且也更新了其对应窗口的状态，就可以根据entry链表更新窗口，这样窗口中保存了正在监控的Entry对象的地址
    update_windows(EntryHeadPtr, slidingWindows);
}


// 全局变量，记录当前发送方的序列号
static uint8_t seq_num = 0;

// 处理输入命令
void handle_input_cmds(Sender *sender, LLnode *head_ptr)
{
    // 获取命令队列的长度
    int input_cmd_length = ll_get_length(&sender->input_cmdlist_head);//
    // 循环处理所有命令
    while (input_cmd_length > 0)
    {
        // 从队列中弹出一个节点，并更新命令队列长度
        LLnode * ll_input_cmd_node = ll_pop_node(&sender->input_cmdlist_head);
        input_cmd_length = ll_get_length(&sender->input_cmdlist_head);

        // 将节点的值转换为 Cmd 类型并释放节点的内存
        Cmd * outgoing_cmd = (Cmd *)ll_input_cmd_node->value;
        if (outgoing_cmd == NULL) {
            return;
        } 
        free(ll_input_cmd_node);

        // 获取消息的长度
        int msg_length = strlen(outgoing_cmd->message);
        //printf("Msg length: %d, Max payload size: %d\n", msg_length, FRAME_PAYLOAD_SIZE);
        if (msg_length >= FRAME_PAYLOAD_SIZE) // 如果消息长度超过帧大小
        {   
            // 分段传输消息
            int offset = 0;
            while (offset < msg_length)
            {
                // 计算当前分段的大小
                int chunk_size = (msg_length - offset > (FRAME_PAYLOAD_SIZE - 1)) ? (FRAME_PAYLOAD_SIZE - 1) : (msg_length - offset);

                // 创建帧
                Frame * outgoing_frame = (Frame *) malloc(sizeof(Frame));
                memset(outgoing_frame, 0, sizeof(Frame));
                // 只清理data区域的缓存可能存在问题**清理缓冲区**
                // memset(outgoing_frame->data, 0, FRAME_PAYLOAD_SIZE); // 清零 data 缓冲区

                //初始化帧
                outgoing_frame->seq_num = seq_num;           // 设置序列号
                outgoing_frame->ack_num = 0;                 // 设置 ACK 号为 0（数据帧）
                outgoing_frame->is_ack = 0;                  // 标记为非 ACK 帧
                outgoing_frame->send_id = sender->send_id;   // 设置发送方 ID
                outgoing_frame->recv_id = outgoing_cmd->dst_id;               // 设置目标接收方 ID（广播）

                // 拷贝消息数据到帧的 data 字段
                strncpy(outgoing_frame->data, outgoing_cmd->message + offset, chunk_size);
                outgoing_frame->data[chunk_size] = '\0'; // 确保字符串以 '\0' 结尾,防止计算到后面的垃圾数据，以0为结尾
                //fprintf(stdout, "outgoing_frame->data = %s\n", outgoing_frame->data);
                
                // 计算校验和
                outgoing_frame->checksum = 0;  // 清零校验和字段，重新计算校验值
                outgoing_frame->checksum = calculate_checksum(outgoing_frame);//这个强制转换只是将将地址转换为了字符流类型
                //fprintf(stdout, "outgoing_frame->checksum = %d\n", outgoing_frame->checksum);
               // printf("********Size of Frame: %zu\n", sizeof(outgoing_frame));
                // 将帧转换为字节流并添加到输出队列
                char * outgoing_charbuf = convert_frame_to_char(outgoing_frame);
            
                //创建一个Entry对象
                WindowEntry *entry = malloc(sizeof(WindowEntry));
                memset(entry, 0, sizeof(WindowEntry));
                //初始化Entry对象
                entry->frame = outgoing_frame;
                entry->charbuf = outgoing_charbuf;
                entry->handle = 0;  //表明此时还没有被发送过
                entry->monitor = 0; //表明此时处于未被监控状态
                //此处不需要更新时间，第一次发送的时候才需要更新
                //gettimeofday(&entry->send_time, NULL); 

                //将Entry对象挂载到发送者线程维护的队列中
                ll_append_node(head_ptr, (void *)entry);
                //fprintf(stdout, "sender head list length = %d\n", ll_get_length(head_ptr));

                // 更新序列号和偏移量
                seq_num = (seq_num + 1) % 256;
                offset += chunk_size;
            }
            //while循环执行完，说明了一条信息已经分帧完成
            free(outgoing_cmd->message);
            free(outgoing_cmd);
        } else {
            // 如果消息长度小于等于帧大小，直接封装成帧
            Frame * outgoing_frame = (Frame *) malloc (sizeof(Frame));
            memset(outgoing_frame, 0, sizeof(Frame));

            // 设置帧的基本字段
            outgoing_frame->seq_num = seq_num;          // 序列号
            outgoing_frame->ack_num = 0;                // 数据帧ACK号为0
            outgoing_frame->is_ack = 0;                 // 非ACK帧
            outgoing_frame->send_id = sender->send_id;  // 发送方ID
            outgoing_frame->recv_id = outgoing_cmd->dst_id;              // 广播（目标接收方ID）

            // 拷贝消息数据到帧的 data 字段
            //strncpy(outgoing_frame->data, outgoing_cmd->message, FRAME_PAYLOAD_SIZE);
            strncpy(outgoing_frame->data, outgoing_cmd->message, strlen(outgoing_cmd->message));//strlen 是不计算最后的尾0
            outgoing_frame->data[strlen(outgoing_cmd->message)] = '\0'; // 确保以 '\0' 结尾
            //fprintf(stdout, "outgoing_frame->data = %s\n", outgoing_frame->data);

            // 计算校验和并设置
            outgoing_frame->checksum = 0; 
            outgoing_frame->checksum = calculate_checksum(outgoing_frame);
            //fprintf(stdout, "outgoing_frame->checksum = %d\n", outgoing_frame->checksum);
            // 将帧转换为字节流
            char * outgoing_charbuf = convert_frame_to_char(outgoing_frame);

            //创建一个Entry对象
            WindowEntry *entry = malloc(sizeof(WindowEntry));
            memset(entry, 0, sizeof(WindowEntry));
            //初始化Entry对象
            entry->frame = outgoing_frame;
            entry->charbuf = outgoing_charbuf;
            entry->handle = 0;                   //表明此时还没有被发送过
            entry->monitor = 0;                  //表明此时处于未被监控状态
            //此处不需要更新时间，第一次发送的时候才需要更新
            //gettimeofday(&entry->send_time, NULL); 

            ll_append_node(head_ptr, (void *)entry);
            //fprintf(stdout, "sender head list length = %d\n", ll_get_length(head_ptr));

            // 更新序列号
            seq_num = (seq_num + 1) % 256;

            // 释放内存
            //free(outgoing_frame);
            free(outgoing_cmd->message);
            free(outgoing_cmd);
        }
    }
}

void send_msg_from_windows(LLnode **windows) {
    char *char_buf = NULL;  //计划指向待发送的信息
    WindowEntry *tmp = NULL;
    LLnode *current = NULL;

    //获得当前时间
    struct timeval current_time;

    //窗口指向的对象，存在两种情况: 第一次发送，超时重传
    for (int i = 0; i < WINDOW_SIZE; i++) {
        current = *(windows + i);
        if (current == NULL)
            continue; // 指向为NULL的窗口直接跳过

        tmp = (WindowEntry *)current->value;
        //做两个判断
        //若是信息未发送过，则发送信息
        if (tmp->handle == 0) {
            tmp->handle = 1;
            // 发送消息到接收方（函数会释放 char_buf）
            char_buf = (char *) malloc(sizeof(char) * MAX_FRAME_SIZE);
            memcpy(char_buf, tmp->charbuf, MAX_FRAME_SIZE);
            send_msg_to_receivers(char_buf);
            // 更新发送时间
            gettimeofday(&tmp->send_time, NULL);
        } else { //只有已经发送过的信息，才需要检测超时重传
            gettimeofday(&current_time, NULL);
            long elapsed_time = timeval_usecdiff(&tmp->send_time, &current_time);
            //fprintf(stdout, "是否会执行超时重传\n");
            if (elapsed_time > TIMEOUT_USEC) {
                //fprintf(stdout, "会执行超时重传\n");
                //说明信息超时，需要重新发送
                char_buf = (char *) malloc(sizeof(char) * MAX_FRAME_SIZE);
                memcpy(char_buf, tmp->charbuf, MAX_FRAME_SIZE);
                send_msg_to_receivers(char_buf);
                // 更新发送时间
                gettimeofday(&tmp->send_time, NULL);
            } 
        }
    }
}

uint8_t If_Windows_Empty(LLnode **slidingWindows) {
    uint8_t window_num = 0;
    LLnode* current = NULL;

    for (int i = 0; i < WINDOW_SIZE; i++) {
        current = *(slidingWindows + i);
        if (current != NULL)
            window_num++;
    }
    return window_num;
}

// 发送方线程的运行函数
void * run_sender(void * input_sender)
{
    struct timespec   time_spec;
    struct timeval    curr_timeval;
    const int WAIT_SEC_TIME = 10;
    const long WAIT_USEC_TIME = 100000;
    Sender * sender = (Sender *) input_sender;    

    //初始化发送者线程管理的窗口数组 和 Entry队列
    LLnode EntryHead = {                    //创建一个LLNode头结点，用于追踪发送者线程维护的Entry链表(双向循环链表)
        .next = &EntryHead,
        .prev = &EntryHead,
        .value = NULL,                      //头结点的value值不需要指向任何东西
    };  //线程管理的 待发送信息的链表。
    LLnode *slidingWindows[WINDOW_SIZE] = {}; //同时发送者线程还会维护一个固定大小的窗口

    // 初始化条件变量和互斥锁
    pthread_cond_init(&sender->buffer_cv, NULL);
    pthread_mutex_init(&sender->buffer_mutex, NULL);

    while(1) {
        // 获取当前时间
        gettimeofday(&curr_timeval, NULL);

        // 设置线程的唤醒时间
        // time_spec 是一个用于指定线程应该唤醒时间的数据结构。
        // 时间被指定为一个绝对时间（意味着，从概念上来说，你指定的是一个具体的时间点，例如 "2010年9月23日下午1点，唤醒"）。
        time_spec.tv_sec  = curr_timeval.tv_sec;
        time_spec.tv_nsec = curr_timeval.tv_usec * 1000;

        // *****************************************************************************************
        // 注意：任何涉及从输入帧或输入命令中出队操作的代码
        // 都应该在互斥锁的加锁和解锁之间完成，因为其他线程可以访问这些结构
        // *****************************************************************************************
        pthread_mutex_lock(&sender->buffer_mutex);

        // 将用户输入的信息进行分帧处理，然后打包成entry对象，挂载到发送者线程维护的outgoing_frames_head链表中
        handle_input_cmds(sender, &EntryHead);

        // 从ACK链表中获取ACK信息的seq_num，根据seq_num从窗口队列中找到待删除的entry链表中的结点，并更新窗口
        handle_incoming_acks(sender, slidingWindows, &EntryHead);

        //如果根据ack list更新了窗口后，窗口数组的元素都指向空，那么说明暂时没有发送的需求，线程可以进入睡眠，直到下一次被唤醒
        if (!If_Windows_Empty(slidingWindows)) { //如果窗口为空，则线程可以进入睡眠
            //fprintf(stdout, "Went to sleep this time\n");
            time_spec.tv_sec += WAIT_SEC_TIME;
            time_spec.tv_nsec += WAIT_USEC_TIME * 1000;
            if (time_spec.tv_nsec >= 1000000000) {
                time_spec.tv_sec++;
                time_spec.tv_nsec -= 1000000000;
            }
            pthread_cond_timedwait(&sender->buffer_cv, &sender->buffer_mutex, &time_spec);
        } 

        //如果窗口不为空，那就需要结合超时重传机制，传入窗口数组，依次发送窗口中的信息
        send_msg_from_windows(slidingWindows);  //也会改到Entry链表中节点的信息

        pthread_mutex_unlock(&sender->buffer_mutex);
    }
    pthread_exit(NULL);
    return 0;
}
